/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ua.yandex.skipass;

import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.atomic.AtomicInteger;
import ua.yandex.collections.MyArrayList;

/**
 *
 * @author vlad
 */
public class SkipassSystem {

    static final int SEASON_DAYS = 90;
    
    private static final int DEFAULT_BLOCK_ARR_SIZE = 1000;
    private final MyArrayList blocked;
    private final LocalDateTime seasonStart;
    private final LocalDateTime seasonEnd;
    private AtomicInteger nextId = new AtomicInteger(0);

    //statistics variables: byTime - 3 passtypes, byCount - 2 passtypes
    private final long[] allowedByTime = new long[PassType.values().length];
    private final long[] allowedByCount
            = new long[PassType.values().length - 1];

    private final long[] deniedByTime = new long[PassType.values().length];
    private final long[] deniedByCount
            = new long[PassType.values().length - 1];

    public SkipassSystem() {
        this.blocked = new MyArrayList();
        blockListInit();
        this.seasonStart = LocalDateTime.now().truncatedTo(ChronoUnit.DAYS);
        this.seasonEnd = this.seasonStart.plusDays(SEASON_DAYS);
    }

    public SkipassSystem(LocalDateTime date) {
        this.blocked = new MyArrayList();
        blockListInit();
        this.seasonStart = date.truncatedTo(ChronoUnit.DAYS);
        this.seasonEnd = this.seasonStart.plusDays(SEASON_DAYS);
    }

    private void blockListInit() {
        for (int i = 0; i < DEFAULT_BLOCK_ARR_SIZE; i++) {
            blocked.add(Boolean.FALSE);
        }
    }
    
    int generateNextId() {
        return nextId.getAndIncrement();
    }
    
    public Skipass produce(PassType type) {
        if (type != PassType.SEASON) {
            throw new IllegalArgumentException("Not enough arguments");
        }
        Skipass pass = new PassByTime(type, this.seasonStart, this.seasonEnd,
                this);
        return pass;
    }

    public Skipass produce(PassType type, LocalDateTime startDate,
            TimeInterval timeInt) {
        if (type == PassType.SEASON) {
            throw new IllegalArgumentException("Season pass is unlimited");
        }
        if (getDatePassType(startDate) != type) {
            throw new IllegalArgumentException("Wrong type for this date");
        }
        if (startDate.isAfter(this.seasonEnd)
                || startDate.isBefore(this.seasonStart)) {
            throw new IllegalArgumentException("Out of season bounds!");
        }
        Object[] supTypes = timeInt.supported();
        boolean supported = false;
        for (Object i : supTypes) {
            if (i == type) {
                supported = true;
            }
        }
        if (!supported) {
            throw new IllegalArgumentException("Such type of skipass "
                    + "is not supported");
        }
        LocalDateTime startTime = startDate.truncatedTo(ChronoUnit.DAYS);
        LocalDateTime endTime = startTime.plus(timeInt.endTime(),
                ChronoUnit.MILLIS);
        if (endTime.isAfter(this.seasonEnd)) {
            endTime = seasonEnd;
        }
        startTime = startTime.plus(timeInt.startTime(), ChronoUnit.MILLIS);
        Skipass pass = new PassByTime(type, startTime, endTime, this);
        return pass;
    }

    public Skipass produce(PassType type, UsesCount uses) {
        if (type == PassType.SEASON) {
            throw new IllegalArgumentException("Season pass is unlimited");
        }
        Object[] supTypes = uses.supported();
        boolean supported = false;
        for (Object i : supTypes) {
            if (i == type) {
                supported = true;
            }
        }
        if (!supported) {
            throw new IllegalArgumentException("Such type of skipass "
                    + "is not supported");
        }
        Skipass pass = new PassByCount(type, uses.count(), this);
        return pass;
    }

    public void block(Skipass pass) {
        blocked.set((int) pass.getUuid(), Boolean.TRUE);
    }
    
    public boolean checkBlock(Skipass pass) {
        return (boolean) blocked.get((int) pass.getUuid());
    }
    
    void incDeniedByTime(int order) {
        deniedByTime[order]++;
    }
    
    void incAllowedByTime(int order) {
        allowedByTime[order]++;
    }
    
    void incDeniedByCount(int order) {
        deniedByCount[order]++;
    }
    
    void incAllowedByCount(int order) {
        allowedByCount[order]++;
    }

    PassType getTodayPassType() {
        return getDatePassType(LocalDateTime.now());
    }

    private PassType getDatePassType(LocalDateTime dateIn) {
        if (dateIn.getDayOfWeek()
                .compareTo(DayOfWeek.FRIDAY) > 0) {
            return PassType.WEEKEND;
        }
        return PassType.WEEKDAY;
    }

    public String getShortInfo() {
        long allowed = 0;
        long denied = 0;
        for (long n : allowedByTime) {
            allowed += n;
        }
        for (long n : allowedByCount) {
            allowed += n;
        }
        for (long n : deniedByTime) {
            denied += n;
        }
        for (long n : deniedByCount) {
            denied += n;
        }
        return String.format("Allowed: %1$10d Denied: %2$10d", allowed, denied);
    }

    public String getInfo() {
        StringBuilder builder = new StringBuilder();
        builder.append("Time-specified passes:\n");
        for (int i = 0; i < PassType.values().length; i++) {
            builder.append('\t');
            builder.append(PassType.values()[i]);
            builder.append(" allowed: ");
            builder.append(String.format("%1$10d ", allowedByTime[i]));
            builder.append("Denied: ");
            builder.append(String.format("%1$10d ", deniedByTime[i]));
        }
        builder.append("\nUse-specified passes:\n");
        for (int i = 0; i < PassType.values().length - 1; i++) {
            builder.append('\t');
            builder.append(PassType.values()[i]);
            builder.append(" allowed: ");
            builder.append(String.format("%1$10d ", allowedByCount[i]));
            builder.append("Denied: ");
            builder.append(String.format("%1$10d ", deniedByCount[i]));
        }
        builder.append('\n');
        return builder.toString();
    }
}
